%option outfile="pylex.c" header-file="pylex.h"
%option noyywrap
%option yylineno

/**
 * Lexical Analizer for Python.
 * This is a prototype for a lexical analyzer for 
 * python programming language v2.4, it currently writes
 * token for sintactical analisys on the stdout.
 * @author Ricchy Alaín Pérez Chevanier
 */

%{
	
	#include <stdarg.h>
	#include <string.h>
	
	/* Structure Node, which represents a single linked list */
	struct Node {
		int value;
		struct Node* next;
	};
	
	/*Functions*/
	
	/* Push a new element into a node representing a single linked list */
	int list_push(struct Node** list, int num);
	
	/* Pop de front element from a single linked list represented by a node */
	int list_pop(struct Node** list);
	
	/* Return the topmost value contained in this single linked list */
	int list_top (struct Node* list);
	
	/* Convert a lowercase string into a uppercase string */
	char* to_capital (char* str);
	
	/* Manage indentation control */
	void manage_indentation();
	
	/* Function that carries out the error report */
	int serror(int line, const char* format,...);

	/* Variables */

	/* Current number of detected errors */
	int numerr;

	/* Current number of lines read */
	int num_lines;

	/* Level of indentation in the current line */
	int cur_line_indent ;

	/* Level of indentation of the current block of code */
	int cur_block_indent;

	/* List that stores indentation levels for differrent block of codes */
	struct Node* indent_list;
	
	/* This integer represents a boolean, which is used to know if the current line is 
 	* is logicla blank line.
 	*/
	int is_blank_line;
	
	/* This integer represents a boolean, which is used to know if we are on context of implicit join */
	int implicit_join;
	
	/* This integer represents a boolean, which is used to know if we are on context of explicit join */
	int explicit_join;
%}

aritm_operator "+"|"-"|"*"|"/"|"%"|"**"|"//"
comp_operator "<"|">"|"<="|">="|"=="|"!="|"<>"
asig_operator "="|"+="|"-="|"*="|"/="|"%="|"**="|"//="|"&="|"|="|"^="|">>="|"<<="
bitw_operator "~"|"&"|"|"|"^"|"<<"|">>"
logic_operator "and"|"or"|"not"
memb_operator "in"|"not in"
id_operator "is"|"is not"

operator {aritm_operator}|{comp_operator}|{asig_operator}|{bitw_operator}
operator_char {logic_operator}|{memb_operator}|{id_operator}

keyword del|for|raise|assert|elif|from|lambda|return|break|else|global|try|class|except|if|while|continue|exec|import|pass|yield|def|finally|print

str_prefix r|u|ur|R|U|UR|Ur|uR

special_char ","|"::"|":"|"@"|"´"|"`"|"."|";"

explicit_join "\\"

imp_join_inic_char "("|"["|"{"

imp_join_end_char ")"|"]"|"}"

int       0|([1-9]{digit}*)|(0[0-7]+)|(0[Xx]({digit}|[a-fA-F])+)
longint   {int}[Ll]
pointf    ({digit}+"."{digit}*)|("."{digit}+)
exponentf (0|([1-9]{digit}*)|{pointf})[eE][+-]?{digit}+
float     {pointf}|{exponentf}
complex   {float}[jJ]
boolean   "True"|"False"
number    {boolean}|{complex}|{float}|{int}|{longint}


identifier ({letter}|_)({letter}|{digit}|_)*

letter {lowercase}|{uppercase}
lowercase [a-z]
uppercase [A-Z]
digit	  [0-9]

%x short_str1
%x short_str2
%x long_str1
%x long_str2
%x comment
%x eat_rest

%%
	/* Comments Managemet */
"#"		{
	BEGIN(comment);
}
<comment>{
	"\n"		{
		num_lines++;
		
		if (!is_blank_line && !implicit_join) {
			printf("NEWLINE\n");
			is_blank_line = 1;
		}
		
		cur_line_indent = 0;
		
		BEGIN(INITIAL);
	}
	.	{}
}


	/* Short String Management */
{str_prefix}?"\""	{
	manage_indentation();
	BEGIN(short_str1);
}
<short_str1>{
	"\""		{ printf("STRING "); BEGIN(INITIAL); }
	"\n"		{
					num_lines++;
					serror(yylineno,"line %d, Illegal linefeed inside a short string", num_lines);
				}
	"\\".		{}
	.			{}
}

{str_prefix}?"'"	{
	manage_indentation();
	BEGIN(short_str2);
}
<short_str2>{
	"'"			{ printf("STRING "); BEGIN(INITIAL); }
	"\n"		{
					num_lines++;
					serror(yylineno,"line %d, Illegal linefeed inside a short string", num_lines);
				}
	"\\".		{}
	.			{}	
}


	/* Long String Management */
{str_prefix}?"\"\"\""	{
	manage_indentation();
	BEGIN(long_str1);
}			
<long_str1>{
	"\"\"\""	{ printf("STRING "); BEGIN(INITIAL); }
	"\n"		{ num_lines++; /* Manage Implicit join */ }
	.			{}
}

{str_prefix}?"'''"	{
	manage_indentation();
	BEGIN(long_str2);
}			
<long_str2>{
	"'''"		{ printf("STRING "); BEGIN(INITIAL); }
	"\n"		{ num_lines++; /* Manage Implicit join */ }
	.			{}
}


	/* Spaces characters management */
"\n"	{
	if (!is_blank_line && !implicit_join) {
		printf("NEWLINE\n");
		is_blank_line = 1;
	} else if(!is_blank_line && explicit_join) {
		printf("NEWLINE\n");
		explicit_join = 0;
		is_blank_line = 1;
	}
	num_lines++;
	cur_line_indent = 0; // Restart line indentation level
}

"\t"	{
	// Advance indentation level in the current line
	if (is_blank_line) {
		int module = cur_line_indent % 8;
		cur_line_indent += 8 - module;
	}

}


" "		{
	// Advance indentation level in the current line
	if (is_blank_line) {
		cur_line_indent += 1;
	}
}


	/* Explicit join management */
"\\"	{
	// Explicite join case
	manage_indentation();
	explicit_join = 1;
	BEGIN(eat_rest);
}
<eat_rest>{
	"\n"	{
		num_lines++;
		BEGIN(INITIAL);
	}
	
	[^"\n"]*	{ serror(yylineno,"No character is allowed after a backslash %s", yytext); }
}


	/* Delimiters management */
{special_char}		{
	manage_indentation();
	printf("%s ",yytext);
}
	/* Implicit join management */
{imp_join_inic_char}	{
	manage_indentation();
	implicit_join++;
	printf("%s ",yytext);
}

{imp_join_end_char}		{
	manage_indentation();
	if (implicit_join)
		implicit_join--;
	printf("%s ",yytext);
}


	/* Operators management */
{operator}	{
	manage_indentation();
	printf("%s ", yytext);
}

{operator_char} 	{
	manage_indentation();
	char* tmp = to_capital(yytext);
	if (tmp!=NULL) {
		printf("%s ", tmp);
		free(tmp);
	} else {
		exit(EXIT_FAILURE);
	}
}


	/* Keyword management */
{keyword}			{
	manage_indentation();
	char* tmp = to_capital(yytext); 
	if (tmp!=NULL) {
		printf("%s ", tmp);
		free(tmp);
	} else {
		exit(EXIT_FAILURE);
	}
}


	/* Number Management */
{number}			{
	manage_indentation();
	printf("NUMBER ");
}


	/* Identifier management */
{identifier}		{
	manage_indentation();
	printf("IDENTIFIER ");
}


	/* Invalid characters managemet */
.	{
	serror(yylineno,"Invalid Character %s", yytext); // Error situation 
}
%%

int list_push(struct Node** list, int num) { 
	if(list == NULL) {
		return -1; // Error Situation
	}
	if(*list == NULL) {
		*list = (struct Node*) malloc(sizeof(struct Node));
		(*list)->value = num;
		(*list)->next = NULL;
	} else  {
		struct Node *new_top = (struct Node*) malloc(sizeof(struct Node));
		if (new_top==NULL) {
			return 0; // Error situation
		}
		new_top->value = num;
		new_top->next = *list;
		*list = new_top;
	}
	return 1; // Success
}

int list_pop(struct Node** list) {
	int elem;
	struct Node* tmp;
	if (list == NULL) {
		return -1;  // For our purpouse this is an error situation
	}
	if(*list == NULL) {
		elem = -1; // For our purpouse this is an error situation
	} else  {
		elem = (*list)->value;
		tmp = *list;
		*list = (*list)->next;
		free(tmp);
	}
	return elem;
}

int list_top (struct Node* list) {
	if(list == NULL) {
		return -1; // For our purpouse this is an error situation
	} else  {
		return list->value;
	}
}

char* to_capital (char* str) {
	int len, i;
	char* res = NULL;
	if (str == NULL) {
		return NULL;
	} else {
		len = strlen (str);
		res = (char*) malloc(sizeof(char)*(len+3));
		strcpy(res, str);
		for (i = 0; i < len; i++) {
			res[i] -= 32;
		}
	}
	return res;
}

void manage_indentation() {
	if (implicit_join) {
		is_blank_line = 0;
		return;
	}
	if (cur_line_indent > cur_block_indent) {
		if(!list_push(&indent_list, cur_block_indent))
			exit(EXIT_FAILURE);
		cur_block_indent = cur_line_indent;
		printf("INDENT ");
	} else if (cur_line_indent < cur_block_indent) {
		int i = 0;
		do {
			if (cur_line_indent == list_top(indent_list)) {
				cur_block_indent = list_pop(&indent_list);
				i++;
				break;
			} else if (cur_line_indent > list_top(indent_list)) {
				i=0;
				if (is_blank_line)
					serror(yylineno,"Illegal indentation", num_lines);
				break;
			} else {
				i++;
				cur_block_indent = list_pop(&indent_list);
				if (cur_line_indent > list_top(indent_list)) {
					i=0;
					if (is_blank_line)
						serror(yylineno,"Illegal indentation", num_lines);
					break;
				}
			}
		} while (cur_line_indent <= list_top(indent_list));
		
		while (i-->0)
			printf("DEDENT ");
	}
	is_blank_line = 0;
}

int serror(int linerr, const char* format,...){
     int n;
     va_list ap;  
 
     va_start(ap,format);
     fprintf(stderr,"%d: error: ",linerr);
     n = vfprintf(stderr,format,ap);
     fprintf(stderr,"\n");  
     va_end(ap);
     
     numerr++;
     return n;
}

/* Main Functon, here is where yylex is call in order to scan a given
 *  in put program
 */
int main(int argc, char* argv[]) {
	if(argc > 1) {
		int i=1;
		while (i<argc) {
			numerr = 0;
			num_lines = 0;
			cur_line_indent = 0;
			cur_block_indent = 0;
			is_blank_line = 1;
			implicit_join = 0;
			explicit_join = 0;
			if ((yyin = fopen(argv[i], "r"))) {
				char* tmp = (char*) malloc(sizeof(char) * (strlen(argv[i])+3));
				if (tmp == NULL) {
					exit(EXIT_FAILURE);
				}
				strcat(strcpy(tmp,argv[i]),"l");
				i++;
				if(freopen(tmp,"w",stdout)) {
					if(freopen(strcat(tmp,"e"),"w",stderr)) {
						yylex();
						while (cur_block_indent > 0) {
							printf("DEDENT ");
							cur_block_indent = list_pop(&indent_list);
						}
						printf("\n");  
						if(numerr)
							fprintf(stderr,"%d errores were found within this file: %s\n",numerr,argv[1]);
					} else {
						fprintf(stderr, "It is impossible to write in error register file: %s",tmp);
						exit(EXIT_FAILURE);
					}
				} else {
					fprintf(stderr,"error: It is impossible to write in output file: %s",tmp);
					exit(EXIT_FAILURE); 
				}
				free(tmp);
			} else {
				fprintf(stderr,"error: It is impossible to read the input file: %s\n",argv[1]);
				exit(EXIT_FAILURE);
			}  
		}
	} else {
      	fprintf(stderr,"error: invalid number of arguments\n"
      			  "you must specify the input file name\n"
			  "example:\n"
			  "pylex input.py\n");
      	exit(EXIT_FAILURE);
   	}
   	exit(EXIT_SUCCESS);
}